1. Introduction
2. Code

1. Introduction

Some programming languages make it possible to treat functional blocks as top level elements - just like classes. These constructs are called closures. Java doesn't have closures, but often this can be replaced by the use of inner classes - for instance when programming Swing user interfaces, ActionListener are often implemented as inner classes.

However, when you do this, you typically have to implement an interface or extend an existing super class.

Let us look at an example of a closure in smalltalk:

#(3 5 7) inject: 2 into: [:inj :val | inj * val]
210

The #(3 5 7) notation is an Array literal, or a list with three elements if you prefer. The Array class has a two parameter method inject: into: The block [:inj :val | inj * val] is executed three times, the result of each execution is fed back into the block in the first variable inj . I've put it in a table to make it clearer.

Parameter Values
Iteration inj val
1 2 3
2 6 5
3 30 7

Java does not offer a closure construct, but it is not impossible to make something that works

What I propose is an abstract class that can be extended and that can wrap any method, not just implementing something defined in the superclass. A few examples:

Closure<Integer> closure = new Closure<Integer>() {
	public Integer product(Integer injected, Integer previousResult) {
		return injected * previousResult;
	}
};
List<Integer> integers = Arrays.asList(3, 5, 7);
Integer result = closure.injectInto(2, integers);

2. Code

2.1. Usage

Closure<String> simpleClosure = new Closure<String>() {
	public String something(String a, Date b) {
		return a + " on : " + b.toString();
	}
};
String result = simpleClosure.apply("buy", new Date());
assertEquals("buy on : Tue May 26 00:00:00 CEST 2009", result);
Closure<Integer> closure = new Closure<Integer>() {
	public Integer sum(Integer... n) {
		if (n.length > 1) {
			int lastIndex = n.length - 1;
			return n[lastIndex] + sum(Arrays.copyOf(n, lastIndex));
		} else {
			return n[0];
		}
	}
};
Integer result = closure.apply(new Integer[] { 1000, 30, 200, 4 });
assertEquals(1234, result);


					

2.1.1. Unit Test

package be.ooxs.examples.closure;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

public class UTestClosures {
	private Date d1;
	private Date d2;
	private Closure<String> simpleClosure = new Closure<String>() {
		@SuppressWarnings("unused")
		public String something(String a, Date b) {
			return a + " on : " + b.toString();
		}
	};

	@Before
	public void createDates() {
		GregorianCalendar calendar = new GregorianCalendar();
		calendar.set(2009, Calendar.MAY, 26, 0, 0, 0);
		d1 = calendar.getTime();
		calendar.set(2009, Calendar.JUNE, 13);
		d2 = calendar.getTime();
	}

	@Test
	public void testIntVarargs() throws Exception {
		Closure<Integer> closure = new Closure<Integer>() {
			public Integer sum(Integer... n) {
				if (n.length > 1) {
					int lastIndex = n.length - 1;
					return n[lastIndex] + sum(Arrays.copyOf(n, lastIndex));
				} else {
					return n[0];
				}
			}
		};
		Integer result = closure.apply(new Integer[] { 1000, 30, 200, 4 });
		assertEquals(1234, result);

		//check autoboxing works
		result = closure.apply(5000, 30, 200, 4);
		assertEquals(5234, result);
	}

	@Test
	public void testSimpleClosure() {
		String result = simpleClosure.apply("buy", d1);
		assertEquals("buy on : Tue May 26 00:00:00 CEST 2009", result);
	}

	@Test
	public void testMixedParameterListWithVarargs() throws Exception {
		Closure<String> closure = new Closure<String>() {
			public String sum(Integer x, Integer y, String... a) {
				StringBuilder b = new StringBuilder();
				b.append(x).append(y);
				for (String string : a) {
					b.append(string);
				}
				return b.toString();
			}
		};
		String result = closure.apply(3, 4, "A", "B", "C", "D");
		assertEquals("34ABCD", result);
	}

	@Test
	public void testApplyAll() {
		List<String> params1 = Arrays.asList("x", "y");
		List<Date> params2 = Arrays.asList(d1, d2);
		List<String> results = simpleClosure.applyAll(params1, params2);
		assertEquals("x on : Tue May 26 00:00:00 CEST 2009", results.get(0));
		assertEquals("y on : Sat Jun 13 00:00:00 CEST 2009", results.get(1));
	}

	@Test
	public void testApplyAll_wrongSize() {
		List<String> params1 = Arrays.asList("x", "y", "one too many");
		List<Date> params2 = Arrays.asList(d1, d2);
		try {
			List<String> results = simpleClosure.applyAll(params1, params2);
			fail("All parameter collections should have same size");
		} catch (Exception e) {
			assertTrue("expecting an exception", true);
		}
	}

	@Test
	public void testInjectInto() {
		Closure<Integer> closure = new Closure<Integer>() {
			public Integer product(Integer injected, Integer previousResult) {
				return injected * previousResult;
			}
		};
		List<Integer> integers = Arrays.asList(100, 2, 5, 7);
		Integer result = closure.injectInto(2, integers);
		assertEquals(2 * 100 * 2 * 5 * 7, result);
	}

	@Test
	public void testInjectInto2() {
		Closure<Integer> closure = new Closure<Integer>() {
			public int product(int injected, int previousResult) {
				return injected * previousResult;
			}
		};
		List<Integer> integers = Arrays.asList(100, 2, 5, 7);
		Integer result = closure.injectInto(2, integers);
		assertEquals(2 * 100 * 2 * 5 * 7, result);
	}

	@Test
	public void testInjectAnnotatedMethod() {
		Closure<Integer> closure = new Closure<Integer>() {
			@Function
			public Integer sumSquares(Integer... values) {
				int result = 0;
				for (int x : values) {
					result += sq(x);
				}
				return result;
			}

			public int sq(int x) {
				return x * x;
			}
		};
		Integer result = closure.apply(2, 7, 3);
		assertEquals(4 + 49 + 9, result);
	}
}